PyBroMo - 1. Simulate 3D trajectories - single core

This notebook is part of PyBroMo a python-based single-molecule Brownian motion diffusion simulator that simulates confocal smFRET experiments.

Overview

In this notebook we show how to perform a 3-D trajectories simulation of a set of freely diffusing molecules. The simulation computes (and saves!) 3-D trajectories and emission rates due to a confocal excitation PSF for each single molecule. Depending on the simulation length, the required disk space can be significant (~ 750MB per minute of simulated diffusion).

For more info see PyBroMo Homepage.

Simulation setup

Together with a few standard python libraries we import PyBroMo using the short name pbm. All PyBroMo functions will be available as pbm.something.


In [ ]:
%matplotlib inline
import numpy as np
import tables
import matplotlib.pyplot as plt
import seaborn as sns
import pybromo as pbm
print('Numpy version:', np.__version__)
print('PyTables version:', tables.__version__)
print('PyBroMo version:', pbm.__version__)

Then we define the simulation parameters:


In [ ]:
# Initialize the random state
rs = np.random.RandomState(seed=1)
print('Initial random state:', pbm.hash_(rs.get_state()))

# Diffusion coefficient
Du = 12.0            # um^2 / s
D1 = Du*(1e-6)**2    # m^2 / s
D2 = D1/2

# Simulation box definition
box = pbm.Box(x1=-4.e-6, x2=4.e-6, y1=-4.e-6, y2=4.e-6, z1=-6e-6, z2=6e-6)

# PSF definition
psf = pbm.NumericPSF()

# Particles definition
P = pbm.Particles(num_particles=20, D=D1, box=box, rs=rs)
P.add(num_particles=15, D=D2)

# Simulation time step (seconds)
t_step = 0.5e-6

# Time duration of the simulation (seconds)
t_max = 1

# Particle simulation definition
S = pbm.ParticlesSimulation(t_step=t_step, t_max=t_max, 
                            particles=P, box=box, psf=psf)

print('Current random state:', pbm.hash_(rs.get_state()))

The most important line is the last line which creates an object S that contains all the simulation parameters (it also contains methods to run the simulation). You can print S and check the current parameters:


In [ ]:
S

Other useful simulation info:


In [ ]:
S.compact_name()

In [ ]:
S.particles.diffusion_coeff_counts

Arrays sizes:


In [ ]:
S.print_sizes()

NOTE: This is the maximum in-memory array size when using a single chunk. In the following, we simulate the diffusion in smaller time windows (chunks), thus requiring only a few tens MB of RAM, regardless of the simulated duration.

Brownian motion simulation

In the brownian motion simulation we keep using the same random state object rs. Initial and final state are saved so the same simulation can be reproduced. See PyBroMo - A1. Reference - Data format and internals.ipynb for more info on the random state.


In [ ]:
print('Current random state:', pbm.hash_(rs.get_state()))

In [ ]:
S.simulate_diffusion(total_emission=False, save_pos=True, verbose=True, rs=rs, chunksize=2**19, chunkslice='times')

In [ ]:
print('Current random state:', pbm.hash_(rs.get_state()))

The normalized emission rate (peaks at 1) for each particle is stored in a 2D pytable array and accessible through the emission attribute:


In [ ]:
print('Simulation file size: %d MB' % (S.store.h5file.get_filesize()/1024./1024.))

In [ ]:
S.store.close()

Load trajectories


In [ ]:
S = pbm.ParticlesSimulation.from_datafile('0168')  # Read-only by default

In [ ]:
S

Plotting the emission

This are basic debug plots. For more advanged interactive exploration of trajectory and emission arrays see the next notebook:


In [ ]:
def plot_emission(S, s=0, size=2e6, slice_=None, em_th=0.01, save=False, figsize=(9, 4.5)):
    if slice_ is None:
        slice_ = (s*size, (s+1)*size)
    slice_ = slice(*slice_)
    em = S.emission[:, slice_]
    dec = 1 if slice_.step is None else slice_.step
    t_step = S.t_step*dec
    t = np.arange(em.shape[1])*(t_step*1e3)
    fig, ax = plt.subplots(figsize=figsize)
    for ip, em_ip in enumerate(em):
        if em_ip.max() < em_th: continue
        plt.plot(t, em_ip, label='P%d' % ip)
    ax.set_xlabel('Time (ms)')
    
    rs_hash = pbm.hash_(S.traj_group._v_attrs['init_random_state'])[:3]
    ax.set_title('%ds ID-EID: %d-%d, sim rs = %s, part rs = %s' %\
              (s, S.ID, S.EID, rs_hash, S.particles.rs_hash[:3]))
    ax.legend(bbox_to_anchor=(1.03, 1), loc=2, borderaxespad=0.)
    if save:
        plt.savefig('em %ds ID-EID %d-%d, rs=%s' %\
                (s, S.ID, S.EID, rs_hash), 
                dpi=200, bbox_inches='tight')
    #plt.close(fig)
    #display(fig)
    #fig.clear()

In [ ]:
plot_emission(S, slice_=(0, 2e6, 10), em_th=0.05)

In [ ]:
def plot_tracks(S, slice_=None, particles=None):
    if slice_ is None:
        slice_ = (0, 100e3, 100)
    duration = (slice_[1] - slice_[0])*S.t_step
    slice_ = slice(*slice_)
    
    if particles is None:
        particles = range(S.num_particles)
    
    fig, AX = plt.subplots(1, 2, figsize=(11, 5), sharey=True)
    plt.subplots_adjust(left=0.05, right=0.93, top=0.95, bottom=0.09,
                        wspace=0.05)
    plt.suptitle("Total: %.1f s, Visualized: %.2f ms" % (
                 S.t_step*S.n_samples, duration*1e3))

    for ip in particles:
        x, y, z = S.position[ip, :, slice_]
        x0, y0, z0 = S.particles[ip].r0
        plot_kwargs = dict(ls='', marker='o', mew=0, ms=2, alpha=0.5, 
                           label='P%d' % ip)
        l, = AX[0].plot(x*1e6, y*1e6, **plot_kwargs)
        AX[1].plot(z*1e6, y*1e6, color=l.get_color(), **plot_kwargs)
        #AX[1].plot([x0*1e6], [y0*1e6], 'o', color=l.get_color())
        #AX[0].plot([x0*1e6], [z0*1e6], 'o', color=l.get_color())

    AX[0].set_xlabel("x (um)")
    AX[0].set_ylabel("y (um)")
    AX[1].set_xlabel("z (um)")

    sig = np.array([0.2, 0.2, 0.6])*1e-6
    ## Draw an outline of the PSF
    a = np.arange(360)/360.*2*np.pi
    rx, ry, rz = (sig)  # draw radius at 3 sigma
    AX[0].plot((rx*np.cos(a))*1e6, (ry*np.sin(a))*1e6, lw=2, color='k')
    AX[1].plot((rz*np.cos(a))*1e6, (ry*np.sin(a))*1e6, lw=2, color='k')
    
    AX[0].set_xlim(-4, 4)
    AX[0].set_ylim(-4, 4)
    AX[1].set_xlim(-4, 4)
    
    if len(particles) <= 20:
        AX[1].legend(bbox_to_anchor=(1.01, 1), loc=2, borderaxespad=0.)

In [ ]:
plot_tracks(S) #particles=[2, 5, 7, 22, 30])

In [ ]:
S.store.close()

Simulate timestamps

Here we simulate some timestamps array one by one. To generate smFRET data in one step (donor + acceptor, single or multiple populations and create Photon-HDF5 files see the next notebook:


In [ ]:
S = pbm.ParticlesSimulation.from_datafile('0168', mode='w')

Simulate timestamps for a single population comprised of all the 35 particles in the diffusion simulation:


In [ ]:
S.simulate_timestamps_mix(max_rates=(200e3,), 
                          populations=(slice(0, 35),),
                          bg_rate=1e3)

Simulate timestamps for a two population (respectively 20 and 15 particles) with different maximum emission rates:


In [ ]:
S.simulate_timestamps_mix(max_rates=(250e3, 180e3), 
                          populations=(slice(0,20), slice(20, 35)),
                          bg_rate=1.2e3)

In [ ]:
S.timestamp_names

Get the timestamps and particles (pytables) arrays:


In [ ]:
ts, part = S.get_timestamps_part('Pop1_P35_Pstart0_max_rate200000cps_BG1000cps_t_1s_rs_bfb867')

Slice to get the data:


In [ ]:
ts[:]

You can find the name of a timestamps array with:


In [ ]:
S.timestamps_match_mix(max_rates=(200e3,), populations=(slice(0, 35),), bg_rate=1e3)

In [ ]: